import numpy

from classfile.class_reader import MethodCodeReader
from heap.system_ref import MethodRef
from insts.inst import Instruction, Index16Instruction


class INVOKE_INTERFACE(Instruction):

    def __init__(self, opcode):
        super().__init__(opcode)
        self.index: numpy.uint16 = None

    def fetchOperands(self, reader: MethodCodeReader):
        self.index = reader.readUint16()
        reader.readUInt8()  # count
        reader.readUInt8()  # zero

    def execute(self, frame):
        cp = frame.getJMethod().getClass().getConstantPool()
        interfaceMethodRef = cp.getConstantValue(self.index)
        interfaceMethod = interfaceMethodRef.resolveInterfaceMethod()
        if interfaceMethod.isStatic() or interfaceMethod.isPrivate():
            raise Exception("Cannot call private or static interface method")
        ref = frame.getOperandStack().getRefFromTop(interfaceMethod.getArgCount() - 1)
        if ref is None:
            raise Exception("Object reference is null")
        if not ref.getKlass().isImplements(interfaceMethod.getKlass()):
            raise Exception("Object reference does not implement the interface")
        method = ref.getKlass().findLocalMethod(interfaceMethod.getName(), interfaceMethod.getDescriptor(), False)
        if method is None:
            raise Exception("Method not found in object reference")
        if method.isAbstract():
            raise Exception("Cannot call abstract method")
        if not method.isPublic():
            raise Exception("Cannot call non-public method")
        self.invokeMethod(frame, method)

    def invokeMethod(self, frame, method):
        thread = frame.getJThread()
        newFrame = thread.newFrame(method)
        thread.pushFrame(newFrame)
        argSlotCount = method.getArgSlotCount()
        if argSlotCount > 0:
            firstIndex = argSlotCount - 1
            for i in range(0, argSlotCount):
                index = firstIndex - i
                arg = frame.getOperandStack().get(index)
                newFrame.getLocalVars().setSlot(i, arg)


class INVOKE_SPECIAL(Index16Instruction):

    def execute(self, frame):
        cp = frame.getJMethod().getClass().getConstantPool()
        methodRef:MethodRef = cp.getConstantValue(self.index)
        method = methodRef.resolveMethod()
        if method.isStatic() or method.isPrivate():
            pass